"""
Define the interface for algorithms to access data
"""
from abc import ABC
from datetime import timedelta
from typing import Dict
from typing import Sequence
from typing import Tuple
from typing import Union

import numpy as np
import pandas as pd

from .graph import Node


class DataLoader(ABC):
    """
    The abstract interface to access data
    """

    @property
    def entities(self) -> Sequence[str]:
        """
        Fetch names of available entities
        """
        raise NotImplementedError

    @property
    def metrics(self) -> Dict[str, Sequence[str]]:
        """
        Fetch available OpenTelemetry as a mapping from entity names to metric names
        """
        raise NotImplementedError

    @property
    def nodes(self) -> Sequence[Node]:
        """
        Pairs of entity and metric
        """
        return [
            Node(entity=entity, metric=metric)
            for entity, metrics in self.metrics.items()
            for metric in metrics
        ]

    def load(
        self,
        entity: str,
        metric: str,
        start: float,
        end: float,
        interval: timedelta,
        **kwargs,
    ) -> Union[None, Sequence[float]]:
        # pylint: disable=too-many-arguments
        """
        Load the time series for the given metric of the given entity

        start: expected start time of the time series,
            which is a unix timestamp in seconds
        end: expected end time of the time series,
            which is a unix timestamp in seconds
        interval: interval between two data points
        """
        raise NotImplementedError

    @staticmethod
    def preprocess(
        time_series: Sequence[Tuple[float, float]],
        start: float,  # Unix timestamp in seconds (Shanghai time)
        end: float,    # Unix timestamp in seconds (Shanghai time)
        interval: timedelta,
        **kwargs,
    ) -> Union[None, Sequence[float]]:
        """
        仅截断时间序列到指定的时间范围内，不进行任何其他处理。
        假设输入数据已标准化、已填充缺失值，并且时间戳是上海时间。
        """
        if not time_series:
            return None  # 如果原始时间序列为空，则返回 None

        # 将时间序列转换为 NumPy 数组，便于截断操作
        data: np.ndarray = np.array(time_series)

        # 只截断时间序列，确保在 [start, end] 边界内
        filtered_data = data[(data[:, 0] >= start) & (data[:, 0] <= end), :]

        if filtered_data.shape[0] == 0:
            return None  # 如果截断后没有数据点，则返回 None

        # 直接返回值部分作为元组
        return tuple(filtered_data[:, 1])


class MemoryDataLoader(DataLoader):
    """
    Implement DataLoader with data in memory
    """

    def __init__(self, data: Dict[str, Sequence[Tuple[float, float]]]):
        """
        data: data[key] is the 2-tuples (timestamp, value)
            key 可以是以下两种格式之一:
            1. "entity|metric" - 包含实体和指标信息的字符串
            2. "0", "1", "2" 等 - 简单的数字索引（向后兼容）
            Timestamp is float, assumed to be in Shanghai time.
        """
        self._data = data
        self._entity_metric_map = {}
        
        # 解析数据字典中的键，提取实体和指标信息
        for key in data.keys():
            if "|" in key:  # 如果键包含"|"分隔符，表示它是"entity|metric"格式
                entity, metric = key.split("|", 1)
                self._entity_metric_map[(entity, metric)] = key

    @property
    def entities(self) -> Sequence[str]:
        """
        提取所有实体名称
        """
        entities = set()
        for key in self._data.keys():
            if "|" in key:
                entity, _ = key.split("|", 1)
                entities.add(entity)
        return tuple(sorted(entities)) if entities else tuple(self._data.keys())

    @property
    def metrics(self) -> Dict[str, Sequence[str]]:
        """
        返回每个实体的指标列表
        """
        result = {}
        for key in self._data.keys():
            if "|" in key:
                entity, metric = key.split("|", 1)
                if entity not in result:
                    result[entity] = []
                result[entity].append(metric)
        
        # 如果没有找到任何实体|指标对，返回一个空字典或默认行为
        if not result and self._data:
            # 向后兼容：如果键全是数字，则假设只有一个默认实体
            default_entity = "default_entity"
            result[default_entity] = tuple(self._data.keys())
        
        # 将列表转换为元组
        return {entity: tuple(metrics) for entity, metrics in result.items()}

    @property
    def nodes(self) -> Sequence[Node]:
        """
        Pairs of entity and metric
        """
        nodes = []
        for key in self._data.keys():
            if "|" in key:
                entity, metric = key.split("|", 1)
                nodes.append(Node(entity=entity, metric=metric))
        
        # 如果没有基于"entity|metric"格式的键，则回退到默认行为
        if not nodes and self._data:
            # 向后兼容：假设一个默认实体
            default_entity = "default_entity"
            nodes = [Node(entity=default_entity, metric=key) for key in self._data.keys()]
        
        return nodes

    def load(
        self,
        entity: str,
        metric: str,
        start: float,  # Unix timestamp in seconds (Shanghai time)
        end: float,    # Unix timestamp in seconds (Shanghai time)
        interval: timedelta,
        **kwargs,
    ) -> Union[None, Sequence[float]]:
        # pylint: disable=too-many-arguments
        
        # 尝试查找"entity|metric"格式的键
        key = f"{entity}|{metric}"
        if key in self._data:
            time_series_data = self._data[key]
        elif (entity, metric) in self._entity_metric_map:
            # 如果在预先解析的映射中找到了
            mapped_key = self._entity_metric_map[(entity, metric)]
            time_series_data = self._data[mapped_key]
        else:
            # 尝试直接使用 metric 作为键（向后兼容）
            if metric in self._data:
                time_series_data = self._data[metric]
            else:
                return None
        
        # 简单地调用预处理方法，只进行时间范围的截断
        return self.preprocess(
            time_series=time_series_data,
            start=start,
            end=end,
            interval=interval
        )
